package util

import java.util.HashMap
import java.util.Random
import java.util.ArrayList
import java.util.LinkedHashMap
import java.util.Collections
import java.util.NoSuchElementException

val rnd = Random()

fun printf(format: String, vararg args: Any?) = System.out.printf(format, *args)

fun <T> Iterable<T>.max(value: T.() -> Int) = reduce { a, b -> if (a.value() > b.value()) a else b }
fun <K, V, T> Iterable<T>.toMap(fn: (T) -> Pair<K, V>): Map<K, V> {
    val result = HashMap<K, V>()
    forEach { result.putAll(fn(it)) }
    return result
}
fun <T> MutableList<T>.swap(i: Int, j: Int) {
    val tmp = this[i]
    this[i] = this[j]
    this[j] = tmp
}
fun <T: Any> MutableList<T>.safeAdd(t: T?) { if (t != null) add(t) }
fun <T> List<T>.shuffledCopy(): List<T> {
    val result = ArrayList<T>(this)
    for (i in 0..(size - 2)) result.swap(i, i + rnd.nextInt(size - i))
    return result
}
fun <T> shuffledList(vararg t: T): List<T> = arrayListOf(*t).shuffledCopy()

fun <K, V> hashMapOf(n: Int, init: (Int) -> Pair<K, V>): HashMap<K, V> {
    val result = HashMap<K, V>()
    for (i in 0..n-1) result.putAll(init(i))
    return result
}

fun <T: Any> menu(title: String, menu: Iterable<T>, format: (T) -> String = { it.toString() }): T? {
    val selection = multiMenu(title, menu, format)
    return if (selection.size() == 1) selection[0] else null
}

fun <T> multiMenu(title: String, menu: Iterable<T>, format: (T) -> String = { it.toString() }): List<T> {
    val index = menu.toList()
    val result = ArrayList<T>()
    println(title)
    val formatPrefix: String = "%${Math.log10(index.size().toDouble()).toInt() + 1}d. "
    index.forEachWithIndex { i, t -> printf(formatPrefix + format(t) + "%n", i + 1) }
    print("Enter number: ")
    val select = readLine()
    if (select != null) {
        for (s in select.trim().split("\\s+")) {
            try {
                val selectIndex = Integer.parseInt(s)
                if (selectIndex in 1..index.size()) result.add(index[selectIndex - 1]) else return Collections.emptyList()
            } catch (e: NumberFormatException) {
                return Collections.emptyList()
            }
        }
    }
    return result
}

fun <K, V> List<V>.mapBy(fn: (V) -> K): Map<K, V> {
    val result = HashMap<K, V>()
    forEach { result.put(fn(it), it) }
    return result
}

fun <T> Iterable<T>.cycle() = CycleIterable(this)


class CycleIterable<T>(val iterable: Iterable<T>) : Iterable<T> {
    public override fun iterator(): Iterator<T> {
        return CycleIterator(iterable)
    }

}

class CycleIterator<T>(val iterable: Iterable<T>) : Iterator<T> {
    var iterator = iterable.iterator()
    val empty = !iterator.hasNext()
    public override fun next(): T {
        if (empty) throw NoSuchElementException()
        if (!iterator.hasNext()) iterator = iterable.iterator()
        return iterator.next()
    }
    public override fun hasNext(): Boolean {
        return !empty
    }

}
